package com.easyhome.dw.timed.task.center.executor.impl;

import com.easyhome.dw.timed.task.center.annotation.XxlJobBeanManage;
import com.easyhome.dw.timed.task.center.executor.XxlDestroyConfig;
import com.easyhome.dw.timed.task.center.executor.XxlJobExecutor;
import com.easyhome.dw.timed.task.center.glue.GlueFactory;
import com.easyhome.dw.timed.task.center.handler.IJobHandler;
import com.easyhome.dw.timed.task.center.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.annotation.AnnotatedElementUtils;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;


/**
 * xxl-job executor (for spring)
 *
 * @author xuxueli 2018-11-01 09:24:52
 */
@Slf4j
public class XxlJobSpringExecutor extends XxlJobExecutor
        implements ApplicationContextAware, SmartInitializingSingleton, DisposableBean {
    @Resource
    private XxlDestroyConfig destroyConfig;

    @Override
    public void afterSingletonsInstantiated() {
        // init JobHandler Repository
        initJobHandlerRepository(applicationContext);

        // init JobHandler Repository (for method)
        initJobHandlerMethodRepository(applicationContext);

        // refresh GlueFactory
        GlueFactory.refreshInstance(1);

        // super start
        try {
            destroyConfig.setExecutor(this);
            super.start();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void destroy() {
        log.info("XxlJobSpringExecutor destroy start");
        super.destroy();
        log.info("XxlJobSpringExecutor destroy end");
    }

    private void initJobHandlerRepository(ApplicationContext applicationContext) {
        if (applicationContext == null) {
            return;
        }
        // init job handler action
        Map<String, IJobHandler> serviceBeanMap = applicationContext.getBeansOfType(IJobHandler.class);

        if (serviceBeanMap.size() > 0) {
            Map<String, IJobHandler> proxyMap = new HashMap<>();
            Map<String, IJobHandler> originMap = new HashMap<>();
            for (IJobHandler handler : serviceBeanMap.values()) {
                log.info("xxl-job JobHandler name:{}", handler.getClass().getSimpleName());
                Class<?> aClass = AopUtils.getTargetClass(handler);
                String name = aClass.getSimpleName();
                if (AopUtils.isCglibProxy(handler) || AopUtils.isAopProxy(handler)) {
                    IJobHandler proxyHandler = proxyMap.get(name);
                    if (proxyHandler != null) {
                        log.error("xxl-job proxyHandler JobHandler conflict name:{}", name);
//                        throw new RuntimeException("xxl-job jobhandler[" + name + "] naming conflicts.");
                    }
                    proxyMap.put(name, handler);
                    registJobHandler(name, handler);
                } else {
                    IJobHandler originHandler = originMap.get(name);
                    if (originHandler != null) {
                        log.error("xxl-job JobHandler conflict name:{}", name);
//                        throw new RuntimeException("xxl-job jobhandler[" + name + "] naming conflicts.");
                    }
                    originMap.put(name, handler);
                    registJobHandler(name, handler);
                }
            }
        }
    }

    private void initJobHandlerMethodRepository(ApplicationContext applicationContext) {
        if (applicationContext == null) {
            return;
        }
        // init job handler from method
        Map<String, Object> xxlBeanMap = applicationContext.getBeansWithAnnotation(XxlJobBeanManage.class);
        for (Map.Entry<String, Object> entry : xxlBeanMap.entrySet()) {
            // get bean
            Object bean;
            Class<?> aClass = AopUtils.getTargetClass(entry.getValue());
            if (aClass == null) {
                log.debug("@XxlJobBeanManage bean manage class is null:{}", entry.getKey());
                continue;
            } else {
                bean = applicationContext.getBean(aClass);
            }

            // filter method
            // referred to ：org.springframework.context.event.EventListenerMethodProcessor.processBean
            Map<Method, XxlJob> annotatedMethods = null;
            try {
                annotatedMethods = MethodIntrospector.selectMethods(bean.getClass(),
                        new MethodIntrospector.MetadataLookup<XxlJob>() {
                            @Override
                            public XxlJob inspect(Method method) {
                                return AnnotatedElementUtils.findMergedAnnotation(method, XxlJob.class);
                            }
                        });
            } catch (Throwable ex) {
                log.error("xxl-job method-jobhandler resolve error for bean[" + "XxlJobBeanManage" + "].", ex);
            }
            if (annotatedMethods == null || annotatedMethods.isEmpty()) {
                continue;
            }

            // generate and regist method job handler
            for (Map.Entry<Method, XxlJob> methodXxlJobEntry : annotatedMethods.entrySet()) {
                Method executeMethod = methodXxlJobEntry.getKey();
                XxlJob xxlJob = methodXxlJobEntry.getValue();
                // regist
                registJobHandler(xxlJob, bean, executeMethod);
            }

        }
    }

    // ---------------------- applicationContext ----------------------
    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        com.easyhome.dw.timed.task.center.executor.impl.XxlJobSpringExecutor.applicationContext = applicationContext;
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    /*
    BeanDefinitionRegistryPostProcessor
    registry.getBeanDefine()
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        this.registry = registry;
    }
    * */

}
